package com.Java.train.business.service.Impl;

import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.json.JSONUtil;
import com.Java.train.business.entity.*;
import com.Java.train.business.entity.DTO.ConfirmOrderReqDTO;
import com.Java.train.business.entity.DTO.ConfirmOrderTicketDTO;
import com.Java.train.business.enums.ConfirmOrderStatusEnum;
import com.Java.train.business.enums.SeatColEnum;
import com.Java.train.business.enums.SeatTypeEnum;
import com.Java.train.business.req.ConfirmOrderDoReq;
import com.Java.train.business.service.TrainService;
import com.Java.train.business.service.daily.DailyTrainCarriageService;
import com.Java.train.business.service.daily.DailyTrainSeatService;
import com.Java.train.business.service.daily.DailyTrainTicketService;
import com.Java.train.common.response.CommonResp;
import com.Java.train.business.entity.DTO.ConfirmOrderDTO;
import com.Java.train.business.req.ConfirmOrderQueryReq;
import com.Java.train.common.exception.BusniessException;
import com.Java.train.common.exception.BusniessExceptionEnum;
import com.Java.train.common.response.PageResp;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.Java.train.business.mapper.ConfirmOrderMapper;
import com.Java.train.business.service.ConfirmOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
//import io.seata.spring.annotation.GlobalTransactional;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import com.Java.train.common.context.LoginMemberContext;
import com.Java.train.common.utils.SnowFlakeUtil;
import org.springframework.beans.factory.annotation.Autowired;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import org.springframework.util.CollectionUtils;
import cn.hutool.core.date.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.Java.train.business.enums.RedisKeyPreEnum.CONFIRM_ORDER;
import static com.Java.train.common.exception.BusniessExceptionEnum.BUSINESS_TICKET_NOT_ENOUGH;

/**
* 确认订单 服务实现类
*
* @author luomoxingchen
* @since 2024-10-04
*/
@Service
public class ConfirmOrderServiceImpl extends ServiceImpl<ConfirmOrderMapper, ConfirmOrder> implements ConfirmOrderService {

    private static final Logger LOG = LoggerFactory.getLogger(ConfirmOrderService.class);

    @Autowired
    ConfirmOrderMapper confirmOrderMapper;
    @Resource
    TrainService trainService;
    @Resource
    DailyTrainTicketService dailyTrainTicketService;
    @Resource
    DailyTrainCarriageService dailyTrainCarriageService;
    @Resource
    DailyTrainSeatService dailyTrainSeatService;
    @Resource
    AfterDoConfirm afterDoConfirm;
    @Resource
    StringRedisTemplate stringRedisTemplate;
    @Resource
    SkTokenServiceImpl skTokenService;
    @Resource
    BeforeConfirmOrderService beforeConfirmOrderService;


    @Override
    public PageResp<ConfirmOrderDTO> queryList(ConfirmOrderQueryReq confirmOrderQueryReq){
        // 1.获取当前用户id信息
        //        Long id = LoginMemberContext.getId();
        // 2.查询数据库中是否存在当前信息（id）
        LOG.info("查询页码：{}", confirmOrderQueryReq.getPage());
        LOG.info("每页条数：{}", confirmOrderQueryReq.getSize());
        PageHelper.startPage(confirmOrderQueryReq.getPage(),confirmOrderQueryReq.getSize());
        List<ConfirmOrder> confirmOrders = list();
        if(confirmOrders == null || CollUtil.isEmpty(confirmOrders)){
            throw new BusniessException(BusniessExceptionEnum.CONFIRM_ORDER_NOT_EXITS);
        }

        // 3 分析分页信息
        // 3.1 获取分页总条数和页数
        PageInfo<ConfirmOrder> pageInfo = new PageInfo<>(confirmOrders);
        LOG.info("总行数：{}", pageInfo.getTotal());
        LOG.info("总页数：{}", pageInfo.getPages());

        // 3.2 转为封装后的返回值并封装到分页返回值中
        List<ConfirmOrderDTO> list = BeanUtil.copyToList(confirmOrders, ConfirmOrderDTO.class);
        PageResp<ConfirmOrderDTO> pageResp = new PageResp<>();
        pageResp.setTotal(pageInfo.getTotal());
        pageResp.setList(list);
        // 4.返回查询到的信息
        return pageResp;
    }

    @Override
    public CommonResp<Object> modify(ConfirmOrder confirmOrder) {
        Long id = confirmOrder.getId();
        DateTime dateTime = DateTime.now();
        if(ObjectUtil.isNull(id)){
            confirmOrder.setId(SnowFlakeUtil.getSnowFlakeNextId());
            confirmOrder.setCreateTime(dateTime);
            confirmOrder.setUpdateTime(dateTime);
            boolean save = save(confirmOrder);
            return new CommonResp<>(save);
        } else {
            confirmOrder.setUpdateTime(dateTime);
            boolean update = updateById(confirmOrder);
            return new CommonResp<>(update);
        }
    }

    @Override
    public CommonResp<Object> remove(String ids) {
        if(StrUtil.isNotEmpty(ids)){
            String[] array = ids.split(",");
            if(!CollectionUtils.isEmpty(Arrays.asList(array))) {
                return new CommonResp<>(confirmOrderMapper.deleteBatchIds(Arrays.asList(array)));
            }
        }
        return new CommonResp<>(Collections.EMPTY_LIST);
    }
     /**
      * @Description: 异步处理订单
      * @param
      * @return
      */
    @Async
//    @GlobalTransactional
    @SentinelResource(value = "doConfirm", blockHandler = "doConfirmBlock")
    public void doConfim(ConfirmOrderReqDTO confirmOrderReqDTO) {
        Date date = confirmOrderReqDTO.getDate();
        String trainCode = confirmOrderReqDTO.getTrainCode();
        Long id = confirmOrderReqDTO.getMemberId();

        // 1.尝试获取锁，分布式锁，限制同一时间只有一个人买票
        String lockKey = CONFIRM_ORDER + date.toString() + '-' + trainCode;
        Boolean ifAbsent = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, lockKey, 600, TimeUnit.SECONDS);
        if(ifAbsent){
           LOG.info("恭喜抢到锁哩");
        } else{
            LOG.info("没抢到锁，有其它消费线程正在出票，不做任何处理");
            return;
        }

        try{
            // 2.循环的方式处理所有订单信息
            while(true){
                // 2.1 获取该待处理订单
                LambdaQueryWrapper<ConfirmOrder> wrapper = new LambdaQueryWrapper<>();
                // 一次查五条
                PageHelper.startPage(1, 5);
                List<ConfirmOrder> list = list(wrapper.eq(ConfirmOrder::getDate, date).eq(ConfirmOrder::getTrainCode, trainCode)
                        .eq(ConfirmOrder::getStatus, ConfirmOrderStatusEnum.INIT.getCode()).orderByAsc(ConfirmOrder::getId));
                // 判断是否为空
                if(CollUtil.isEmpty(list)){
                    LOG.info("订单列表为空");
                    break;
                } else {
                    LOG.info("本次处理{}条订单", list.size());
                }

                LOG.info("开始按消息进入顺序处理订单");
                list.forEach(confirmOrder -> {
                    try{
                        sell(confirmOrder);
                    } catch (BusniessException e){
                        if(e.getE().equals(BusniessExceptionEnum.BUSINESS_TICKET_NOT_ENOUGH)){
                            LOG.info("本订单余票不足，继续售卖下一个订单");
                            confirmOrder.setStatus(ConfirmOrderStatusEnum.EMPTY.getCode());
                            updateById(confirmOrder);
                        } else {
                            throw e;
                        }
                    }
                });
            }
        } finally {
            // 订单完成删除key
            stringRedisTemplate.delete(lockKey);
        }

    }
     /**
      * @Description: 统计订单之前排队人数
      * @param
      * @return
      */
    @Override
    public Integer queryOrderCount(Long id) {
        // 查询比id靠前状态为初始化的订单数
        ConfirmOrder confirmOrder = getById(id);
        ConfirmOrderStatusEnum orderStatusEnum = EnumUtil.getBy(ConfirmOrderStatusEnum::getCode, confirmOrder.getStatus());
        Integer result = switch (orderStatusEnum){
            case PENDING -> 0; // 处理中
            case SUCCESS -> -1; // 订单成功
            case FAILURE -> -2; // 失败
            case EMPTY -> -3; // 没有票
            case CANCEL -> -4;// 取消
            case INIT -> 999;// 初始化
        };

        Date date = confirmOrder.getDate();
        String trainCode = confirmOrder.getTrainCode();
        Date createTime = confirmOrder.getCreateTime();

        if(result == 999){
            //查询同一日期、车次、比当前订单早并且状态为INIT或PENDING的订单数量
            LambdaQueryWrapper<ConfirmOrder> wrapper1 = new LambdaQueryWrapper<>();
            Long count = count(wrapper1.eq(ConfirmOrder::getDate, date).eq(ConfirmOrder::getTrainCode, trainCode)
                    .lt(ConfirmOrder::getCreateTime, createTime).eq(ConfirmOrder::getStatus, ConfirmOrderStatusEnum.INIT.getCode()));
            LambdaQueryWrapper<ConfirmOrder> wrapper2 = new LambdaQueryWrapper<>();
            count += count(wrapper2.eq(ConfirmOrder::getDate, date).eq(ConfirmOrder::getTrainCode, trainCode)
                    .lt(ConfirmOrder::getCreateTime, createTime).eq(ConfirmOrder::getStatus, ConfirmOrderStatusEnum.PENDING.getCode()));
            return count.intValue();
        } else {
            return result;
        }
    }

    @Override
    public Long cancelOrder(Long id) {
        LambdaUpdateWrapper<ConfirmOrder> wrapper = new LambdaUpdateWrapper<>();
        boolean update = update(wrapper.set(ConfirmOrder::getStatus, ConfirmOrderStatusEnum.CANCEL.getCode()).eq(ConfirmOrder::getId, id)
                .eq(ConfirmOrder::getStatus, ConfirmOrderStatusEnum.INIT.getCode()));

        if(update){
            return 1L;
        }else {
            return 0L;
        }
    }

    /**
      * @Description: 根据车票处理座位问题
      * @param
      * @return
      */

    private void sell(ConfirmOrder confirmOrder) {
//        try {
//            Thread.sleep(200);
//        } catch (Exception e){
//            throw new RuntimeException(e);
//        }
        // 修改订单状态为处理中
        confirmOrder.setStatus(ConfirmOrderStatusEnum.PENDING.getCode());
        updateById(confirmOrder);

        // 2.1 初始化会员订单信息
        Date date = confirmOrder.getDate();
        String trainCode = confirmOrder.getTrainCode();
        String start = confirmOrder.getStart();
        String end = confirmOrder.getEnd();
//            ConfirmOrder confirmOrder = new ConfirmOrder();
//            confirmOrder.setId(SnowFlakeUtil.getSnowFlakeNextId());
//            confirmOrder.setMemberId(LoginMemberContext.getId());
//            confirmOrder.setDate(date);
//            confirmOrder.setTrainCode(trainCode);
//            confirmOrder.setStart(start);
//            confirmOrder.setEnd(end);
//            confirmOrder.setDailyTrainTicketId(confirmOrderDoReq.getDailyTrainTicketId());
//            confirmOrder.setTickets(JSON.toJSONString(confirmOrderDoReq.getTickets()));
//            confirmOrder.setStatus(ConfirmOrderStatusEnum.INIT.getCode());
//            confirmOrder.setCreateTime(now);
//            confirmOrder.setUpdateTime(now);
//            LOG.info("订单信息如下：{}", confirmOrder);
//            save(confirmOrder);

        LambdaQueryWrapper<DailyTrainTicket> ticketWrapper = new LambdaQueryWrapper<>();
        DailyTrainTicket ticket = dailyTrainTicketService.getOne(ticketWrapper.eq(DailyTrainTicket::getDate, date).eq(DailyTrainTicket::getTrainCode, trainCode)
                .eq(DailyTrainTicket::getStart, start).eq(DailyTrainTicket::getEnd, end));

        // 2.2 查询余票数量，模拟扣减，判断是否充足
        int virYdz = ticket.getYdz(), virEdz = ticket.getEdz(), virRw = ticket.getRw(), virYw = ticket.getYw();


        // 类型改变（将entity的String类JSON字符串转为List数组
        List<ConfirmOrderTicketDTO> tickets = JSONUtil.toList(confirmOrder.getTickets(), ConfirmOrderTicketDTO.class);
        for (ConfirmOrderTicketDTO ticketDTO : tickets) {
            String seatTypeCode = ticketDTO.getSeatTypeCode();
            SeatTypeEnum typeEnum = EnumUtil.getBy(SeatTypeEnum::getCode, seatTypeCode);
            // 消减虚拟票数（没有存到数据库）
            ticketCountDecrease(ticket, typeEnum);
        }

        // 3.选座
        // 3.1 获取所有车票，并查看是否选座（看车票第一个值就行）
        ConfirmOrderTicketDTO confirmOrderTicketDTO0 = tickets.get(0);
        String seatTypeCode = confirmOrderTicketDTO0.getSeatTypeCode();
        // 获取起始站点和终止站点
        Integer startIndex = ticket.getStartIndex();
        Integer endIndex = ticket.getEndIndex();
        List<DailyTrainSeat> finalSeatList = new ArrayList<>();
        if (StrUtil.isBlank(confirmOrderTicketDTO0.getSeat())) {
            LOG.info("会员没有选座");
            for (ConfirmOrderTicketDTO orderTicketDTO : tickets) {
                genSeat(finalSeatList, date, trainCode, seatTypeCode, null, null, startIndex, endIndex);
            }
        } else {
            LOG.info("会员开始选座");
            // 3.2 按座位类型创建选座列表
            // 构建选座列表（计算最终偏移量）
            List<String> referSeatList = new ArrayList<>();
            List<SeatColEnum> seatColEnums = SeatColEnum.getColsByType(seatTypeCode);
            LOG.info("本次选座所包含的列数为：{}", seatColEnums);
            for (int i = 1; i <= 2; i++) {
                for (SeatColEnum seatColEnum : seatColEnums) {
                    referSeatList.add(seatColEnum.getDesc() + i);
                }
            }
            LOG.info("用于作参照的两排座位：{}", referSeatList);
            // 3.3 计算每个座位相对第一个选座位置的偏移量
            // 3.3.1 获取所有座位的绝对偏移值
            List<Integer> aboluteOffsetList = new ArrayList<>();
            for (ConfirmOrderTicketDTO ticketDTO : tickets) {
                aboluteOffsetList.add(referSeatList.indexOf(ticketDTO.getSeat()));
            }
            // 3.3.2 获取所有座位相对第一个座位的偏移
            List<Integer> offsetList = new ArrayList<>();
            for (Integer index : aboluteOffsetList) {
                offsetList.add(index - aboluteOffsetList.get(0));
            }
            LOG.info("相对偏移座位数组为：{}", offsetList);
            // 3.4 筛选出每日对应所有车厢
            genSeat(finalSeatList, date, trainCode, seatTypeCode,
                    confirmOrderTicketDTO0.getSeat().split("")[0], offsetList,
                    startIndex, endIndex);
        }
        // 4. 事务处理购票后的数据
        // 4.1 修改座位sell信息，扣减余票信息中的数量
        try {
            afterDoConfirm.afterDofirm(ticket, finalSeatList, tickets, confirmOrder);
        } catch (Exception e) {
            LOG.info("保存购票信息失效", e);
            throw new BusniessException(BusniessExceptionEnum.BUSINESS_CONFIRM_ORDER_EXCEPTION);
        }
    }

    /**
      * @Description: 生成座位
      * @param
      * @return
      */
    private void genSeat(List<DailyTrainSeat> finalSeatList, Date date, String trainCode, String seatType, String colmn,
                         List<Integer> offsetList, Integer startIndex, Integer endIndex){
        List<DailyTrainSeat> genSeatList = new ArrayList<>(); // 以第一个座位为起始的临时最终数组，若偏移获取完就存储到最终数组中
        // 1. 筛出符合座位等次的车厢
        List<DailyTrainCarriage> trainCarriages = dailyTrainCarriageService
                .selectCarriageBySeatType(date, trainCode, seatType);
        LOG.info("一共查出{}个符合条件的车厢", trainCarriages.size());

        // 2.根据车厢筛出对应座位
        for (DailyTrainCarriage trainCarriage : trainCarriages) {
            genSeatList = new ArrayList<>(); // 每次车厢需要初始化最终车座数组
            Integer trainCarriageIdx = trainCarriage.getIdx();
            LOG.info("开始筛选车厢号为{}的车座", trainCarriageIdx);
            // 2.1 实际筛每日座位
            List<DailyTrainSeat> dailyTrainSeats = dailyTrainSeatService
                    .selectByCarriageIdx(date, trainCode, trainCarriageIdx);
            LOG.info("获取车厢{},实际车座数量为{}", trainCarriage.getIdx(), dailyTrainSeats.size());

            // 2.2 遍历座位获取符合要求的座位
            for (DailyTrainSeat dailyTrainSeat : dailyTrainSeats) {
                // 2.2.1 获取当前第一个所选座位并判断
                String col = dailyTrainSeat.getCol();
                Integer firstSeatIdx = dailyTrainSeat.getCarriageSeatIndex();

                // 判断当前座位不能被选中过
                boolean alreadyChooseFlag = false;
                for (DailyTrainSeat finalSeat : finalSeatList){
                    if (finalSeat.getId().equals(dailyTrainSeat.getId())) {
                        alreadyChooseFlag = true;
                        break;
                    }
                }
                if (alreadyChooseFlag) {
                    LOG.info("座位{}被选中过，不能重复选中，继续判断下一个座位", firstSeatIdx);
                    continue;
                }

                if(StrUtil.isBlank(colmn)){
                    LOG.info("无座位");
                } else{
                    LOG.info("选中第一个座位为：{}", firstSeatIdx);
                    if(!colmn.equals(col)){
                        LOG.info("本座位与当前座位不匹配，本座位为：{}，需求座位为：{}", col, colmn);
                        continue;
                    }
                }

                // 2.2.2 判断第一个座位sell是否合法
                boolean isChosen = calSell(dailyTrainSeat, startIndex, endIndex);
                boolean isAllChosen = true;
                if(isChosen){
                    genSeatList.add(dailyTrainSeat); // 第一座位合法加入数组
                    LOG.info("选中座位");
                    // 2.2.3 根据偏移量判断是否合规
                    if(CollUtil.isNotEmpty(offsetList)) {
                        for (int i = 1; i < offsetList.size(); i++) {
                            // 获取偏移后的索引值
                            // 座位索引值从1开始，偏移量数组从0开始
                            int offsetSeatIdx = firstSeatIdx + offsetList.get(i);
                            // 按偏移长度与座位表长比较
                            if (offsetSeatIdx > dailyTrainSeats.size()) {
                                LOG.info("座位不可选，所选座位已经超过该车厢座位,错误座位号为：{}", offsetSeatIdx);
                                isAllChosen = false;
                                break;
                            }
                            // 查询offset偏移座位
                            LambdaQueryWrapper<DailyTrainSeat> wrapper = new LambdaQueryWrapper<>();
                            DailyTrainSeat offsetSeat = dailyTrainSeatService.getOne(wrapper.eq(DailyTrainSeat::getDate, date).eq(DailyTrainSeat::getTrainCode, trainCode)
                                    .eq(DailyTrainSeat::getCarriageIndex, trainCarriageIdx).eq(DailyTrainSeat::getCarriageSeatIndex, offsetSeatIdx));
                            // 判断是否合法
                            if (!calSell(offsetSeat, startIndex, endIndex)) {
                                LOG.info("本座位已售，无法选中：座位号为{}", offsetSeat.getCarriageSeatIndex());
                                isAllChosen = false;
                                break;
                            }

                            genSeatList.add(offsetSeat);
                            LOG.info("选中本座为：{}", offsetSeatIdx);
                        }
                    } else {
                        LOG.info("偏移量数组为空，即不存在选座");
                    }
                    if(!isAllChosen){
                        genSeatList = new ArrayList<>(); // 不是所有车座都合法，即需要初始化
                        continue;
                    } else {
                        finalSeatList.addAll(genSeatList);
                        LOG.info("最终的座位数据为：{}", finalSeatList);
                        LOG.info("选票成功捏");
                        return;
                    }
                } else {
//                    LOG.info("未选中座位");
                    continue;
                }
            }
        }
    }

     /**
      * @Description: 查找合适的座位，并给Sell赋值
      * @param
      * @return
      */
    private boolean calSell(DailyTrainSeat dailyTrainSeat, Integer startIndex, Integer endIndex) {
        String sell = dailyTrainSeat.getSell();
        String sellPart = sell.substring(startIndex, endIndex);
        LOG.info("本车座当前的起始与终止车站编号为：{}，{}", startIndex, endIndex - 1);
        LOG.info("本车座位的售卖情况为：{}", sell);
        if(Integer.parseInt(sellPart) > 0){
            LOG.info("车座{}在本次车站区间{}~{}已经售出", dailyTrainSeat, startIndex, endIndex);
            return false;
        } else {
            LOG.info("车座{}在本次车站区间{}~{}未售出", dailyTrainSeat, startIndex, endIndex);
            String curSell = sellPart.replace('0', '1');
            // 填充子串之前‘0’
            curSell = StrUtil.fillBefore(curSell, '0', endIndex);
            // 填充子串之后‘0’
            curSell = StrUtil.fillAfter(curSell, '0', sell.length());
            LOG.info("填充之后的收票情况为：{}", curSell);

            // 将卖出去票的二进制串与数据库中sell进行与操作
            int newSellInt = NumberUtil.binaryToInt(sell) | NumberUtil.binaryToInt(curSell);
            String newSell = NumberUtil.getBinaryStr(newSellInt);
            // 与后的字符串可能会出现前位0丢失,因此需要补齐
            newSell = StrUtil.fillBefore(newSell, '0', sell.length());
            dailyTrainSeat.setSell(newSell);
            LOG.info("与操作之后的真是sell信息为:{}", newSell);
            return true;
        }
    }

    /**
      * @Description: 删除对应选票数量的车票并判断余票是否充足
      * @param
      * @return
      */
    private void ticketCountDecrease(DailyTrainTicket ticket, SeatTypeEnum typeEnum) {
        switch (typeEnum){
            case YDZ -> {
                Integer ydz = ticket.getYdz() - 1;
                if(ydz < 0){
                    throw new BusniessException(BUSINESS_TICKET_NOT_ENOUGH);
                }
                ticket.setYdz(ydz);
            }
            case EDZ -> {
                Integer edz = ticket.getEdz() - 1;
                if(edz < 0){
                    throw new BusniessException(BUSINESS_TICKET_NOT_ENOUGH);
                }
                ticket.setEdz(edz);
            }
            case RW -> {
                Integer rw = ticket.getYdz() - 1;
                if(rw < 0){
                    throw new BusniessException(BUSINESS_TICKET_NOT_ENOUGH);
                }
                ticket.setRw(rw);
            }
            case YW -> {
                Integer yw = ticket.getYdz() - 1;
                if(yw < 0){
                    throw new BusniessException(BUSINESS_TICKET_NOT_ENOUGH);
                }
                ticket.setYdz(yw);
            }
        }
    }

     /**
      * @Description: 限流之后的处理
      * @param
      * @return
      */
    private void doConfirmBlock(ConfirmOrderDoReq req, BlockException e){
        LOG.info("购票被限流：{}", req);
        throw new BusniessException(BusniessExceptionEnum.CONFIRM_ORDER_FLOW_EXCEPTION);
    }

}


